import json
import pprint
from datetime import datetime, timedelta, time
from sqlalchemy.orm import Session
from core.response import resp_500
from core.logger import logger

from app.notice_center.models.nc_n9e_alarm_record import NCN9eAlarmRecord
from app.manage_notice.models.manage_notice_department import ManageNoticeDepartment
from app.manage_notice.models.manage_notice_team import ManageNoticeTeam
from common.utils import CommonTimeUtils
from common.notice_tools import NoticeTools

from setting import config


class NCN9eAlarmHandler():
    """ NCN9eAlarm  Handler
    """
    def __init__(self):
        self.notice_tools = NoticeTools()


    def check_alert_notice_again(self, alert_info, record_info):
        # 第6次告警时，减少告警频率，此时升级到领导至少已2次
        # record_info["number"] 第几次告警
        # record_info["notice_count"] 已通知的次数
        # 告警的次数会比已通知的次数至少大1
        if alert_info["status"] == "firing" and record_info["notice_count"] >= 5:
            if (record_info["number"]+1) % 6 != 0:
                logger.info(f"Alarm again at interval time")
                return False

            # 需要再次提醒
            return True

        # 不属于再次告警的场景
        return True


    def check_alert_notice_maintain(self, record_info):
        # 没有维护时间
        if not record_info["maintain_begin"] or not record_info["maintain_end"]:
            logger.info(f"name:{record_info['name']} maintain is null")
            return True

        # 检查是否在维护时间内
        maintain_begin = CommonTimeUtils.strptime(record_info["maintain_begin"])
        maintain_end = CommonTimeUtils.strptime(record_info["maintain_end"])
        if CommonTimeUtils.check_between_datetime(maintain_begin, maintain_end) :
            logger.info(f"name:{record_info['name']} in maintain")
            return False

        logger.info(f"name:{record_info['name']} not in maintain")
        return True



    def reduce_alarm_notice_level(self, db: Session, alert_info: dict, record_info: dict):
        """
        告警降噪: 同一告警规则，critical、major、warning级别的，告警发生5次以上的告警级别降为info。
        """
        # 不需要降级
        if alert_info["level"] not in ["critical", "major", "warning"]:
            return True
        
        # 已经告警的直接用上次的级别
        rule_number = 5
        if record_info["notice_count"] == 0:
            # 第一次告警
            # 已通知的告警是否超过rule_number，否则告警级别降为info
            firing_number = NCN9eAlarmRecord.get_object_firing_number(db, alert_info)
            if firing_number >= rule_number:
                alert_info["level"] = "info"
                record_info["level"] = "info"
                logger.info(f"The {alert_info['name']} has notified more than {rule_number}, level change to info")
        else:
            # 后续告警
            # 告警规则被修改过的，需要查看是否可以恢复
            if alert_info["level"] != record_info["level"]:
                firing_number = NCN9eAlarmRecord.get_object_firing_number(db, alert_info)
                if firing_number < rule_number and alert_info["status"] == "firing":
                    record_info["level"] = alert_info["level"]
                    logger.info(f"The {alert_info['name']} alarm level recover to {alert_info['level']}")
                else:
                    # 继续保持告警降级
                    alert_info["level"] = record_info["level"]
        
        return True
    
        

    def check_alert_notice(self, db: Session, alert_info: dict, record_info: dict):
        # 通过降低告警级别来降噪，防止手机轰炸。
        self.reduce_alarm_notice_level(db, alert_info, record_info)
        
        # 之前已发送过告警，则恢复时必须通知
        if record_info["notice_count"]>0 and  alert_info["status"] == "resolved":
            return True
            
        # 告警级别为nothing，则不告警
        if alert_info["level"] == "nothing":
            logger.info(f"n9e alarm level is nothing")
            return False
        
        # 是否在告警规则的维护时间
        if not self.check_alert_notice_maintain(record_info):
            logger.info(f"Alarm in maintain")
            return False
        
        # 检查全局告警屏蔽的配置，全局维护：不告警、不回调
        global_record_info = NCN9eAlarmRecord.get_object_info_for_block_global(db)
        if global_record_info and not self.check_alert_notice_maintain(global_record_info):
            logger.info(f"block global alarm in maintain")
            return False
        
        # 减少告警频率
        if not self.check_alert_notice_again(alert_info, record_info):
            logger.info(f"alarm not notice again")
            return False
        
        # 可以通知
        logger.info(f"alert can be notice")
        return True


    def is_upgrade_to_department(self, alert_info: dict, record_info: dict):
        # 告警级别为info或者warning，则不升级
        if alert_info["level"] in ["info","warning"]:
            return False
        
        # 告警时
        if alert_info["status"] == "firing":
            # 告警级别为major，第4次告警时及以后，则升级到部门领导
            if alert_info["level"] == "major" and record_info["notice_count"] >= 3:
                return True
            
            # 告警级别为critical，第3次告警及以后，则升级到部门领导
            if alert_info["level"] == "critical" and record_info["notice_count"] >= 2:
                return True
            
        # 恢复时
        if alert_info["status"] == "resolved":
            # 告警级别为major，告警已升级后，恢复才发送到部门领导
            if alert_info["level"] == "major" and record_info["notice_count"] > 3:
                return True
            
            # 告警级别为critical，告警已升级后，恢复才发送到部门领导
            if alert_info["level"] == "critical" and record_info["notice_count"] > 2:
                return True
            
        return False

    def alarm_upgrade_to_department(self, db: Session, alert_info: dict, record_info: dict):
        """
        告警升级到部门领导
        """
        if self.is_upgrade_to_department(alert_info, record_info):
            # 告警通知人升级
            department_name = alert_info["department"]
            department_object = ManageNoticeDepartment.get_object_by_name(db, department_name)
            if not department_object:
                logger.error(f"get department by name:{department_name} failed")
                return alert_info["notify_users"]
            
            # 添加部门负责人
            department_info = ManageNoticeDepartment.get_object_info_by_name(db, department_name)
            for notice_team_uuid in department_info["notice_teams_uuid"]:
                notice_team_info = ManageNoticeTeam.get_object_info_by_uuid(db, notice_team_uuid)
                alert_info["notify_users"].extend([
                    {
                        "name": user["name"],
                        "telephone": user["telephone"],
                        "mailbox": user["mailbox"],
                        "comment": "platform user",
                    }
                    for user in notice_team_info["users"]
                ])
            
            logger.info(f"alarm upgrade to department success")
        return alert_info["notify_users"]




    def get_notice_info(self, db: Session, alert_info: dict, record_info: dict):
        """
        获取通知信息
        """
        notice_info = {
            "sms": [],
            "voice": [],
            "mailboxs": [],
            "chat_users": []
        }
        # 告警升级到部门领导
        notify_users = self.alarm_upgrade_to_department(db, alert_info, record_info)
        
        # 重组返回数据结构
        for user_info in notify_users:
            if user_info["name"] not in notice_info["chat_users"]:
                notice_info["chat_users"].append(user_info["name"])
                
                # 电话号码
                if user_info["telephone"]:
                    notice_info["sms"].append(user_info["telephone"])
                    notice_info["voice"].append({"username": user_info["name"], "mobile": user_info["telephone"]})
                else:
                    logger.warning(f"user:{user_info['name']} not configured with a phone number")
                    
                # 邮箱
                notice_info["mailboxs"].append(user_info["mailbox"])

        logger.info(f"get notice info success")
        return notice_info






    def send_notice_wechat(self, notice_msg_card: str, notice_msg_markdown: str, alert_info: dict):
        """
        根据企业微信群的配置，将通知信息发送到群
        """
        n9e_cluster_config = config.NOTICE_CENTER_N9E_GROUP.dict()
        if alert_info["cluster"] not in n9e_cluster_config:
            logger.error(f"cluster:{alert_info['cluster']} not configured wechat")
            return False

        for group_config in n9e_cluster_config[alert_info["cluster"]]:
            if group_config["classify"] == "card" and alert_info["notice_type"] == "alarm":
                # 发送企业微信小程序的卡片信息，在小程序那边生成工单，订阅消息不发送到卡片
                notice_msg_card["chat_id"] = group_config["group_id"]
                result = self.notice_tools.send_wechat_card(notice_msg_card)
                if not result:
                    logger.error(f"send wechat card failed")
                else:
                    logger.info(f"send wechat card success")
                
                continue
            
            if group_config["classify"] == "bot":
                # 发送到大群
                result = self.notice_tools.send_wechatbot(group_config["group_id"], notice_msg_markdown)
                if not result:
                    logger.error(f"send wechat bot failed")
                else:
                    logger.info(f"send wechat bot success")
                    
                continue
            
        return True





    def send_notice(self, db: Session, alert_info, notice_info, record_info):
        notice_msg = self.notice_tools.n9e_alarm_message(alert_info, notice_info, record_info)
        notice_msg_markdown = self.notice_tools.n9e_alarm_message_markdown(alert_info, notice_info, record_info)
        notice_msg_card = self.notice_tools.n9e_alarm_message_card(alert_info, notice_info, record_info)

        # 发送企业微信到个人，info（通知告警级）别都会发
        result = self.notice_tools.send_wechat(notice_info["chat_users"], notice_msg_markdown, "markdown")
        if not result:
            logger.error(f"send wechat failed, chat_users=>{notice_info['chat_users']}")
        else:
            logger.info(f"send wechat success, chat_users=>{notice_info['chat_users']}")

        # 发送到企业微信群, warning（一般告警）级别以上
        if alert_info["level"] in ["warning", "major", "critical"]:
            result = self.send_notice_wechat(notice_msg_card, notice_msg_markdown, alert_info)
            if not result:
                logger.error(f"send wechat group failed")
            else:
                logger.info(f"send wechat group success")

        # 发送短信告警，warning（一般告警）级别以上， 并且不是工作时间才发送（节省费用）
        if alert_info["level"] in ["warning", "major", "critical"] and not CommonTimeUtils.check_now_is_work_time():
            result = self.notice_tools.send_sms(notice_info["sms"], notice_msg)
            if not result:
                logger.error(f"send sms failed, sms=>{notice_info['sms']}")
            else:
                logger.info(f"send sms success, sms=>{notice_info['sms']}")

        # 发送电话告警，major级别以上，只有告警时才打电话
        if alert_info["level"] in ["major", "critical"] and alert_info["status"] == 'firing':
            result = self.notice_tools.send_voice(notice_info["voice"])
            if not result:
                logger.error(f"send voice failed, voice=>{notice_info['voice']}")
            else:
                logger.info(f"send voice success, voice=>{notice_info['voice']}")

        # 更新通知次数
        record_info["notice_count"] += 1
        record_info["notify_users"] = alert_info["notify_users"]
        result = NCN9eAlarmRecord().update(db, record_info)
        if not result:
            logger.error(f"update n9e alarm record failed")
            return False

        logger.info(f"send notice all success =>\n{notice_msg}")
        return True


    def alert_to_notice(self, db: Session, alert_info: dict, record_info: dict):
        # 检查是否需要通知
        if not self.check_alert_notice(db, alert_info, record_info):
            logger.info(f"alert not to notice ")
            return True

        # 获取通知人的信息
        notice_info = self.get_notice_info(db, alert_info, record_info)
        if not notice_info["chat_users"]:
            logger.error(f"get notice info is None")
            return False

        # 发送告警信息
        result = self.send_notice(db, alert_info, notice_info, record_info)
        if not result:
            logger.info(f"send notice failed")
            return False

        logger.info(f"alert to notice success")
        return True


    def alert_to_database_firing(self, db: Session, alert_info: dict):
        # 收到告警信息
        record_info = NCN9eAlarmRecord.get_object_info_by_alert(db, alert_info, "firing")
        if not record_info:
            # 第一条告警信息，在数据库中新建一条记录
            data = {
                "name": alert_info["name"],
                "cluster": alert_info["cluster"],
                "classify": alert_info["classify"],
                "status": alert_info["status"],
                "target": alert_info["target"],
                "number": 1,
                "fingerprint": alert_info["fingerprint"],
                "level": alert_info["level"],
                "notify_users": alert_info["notify_users"],
                "notice_count": 0,
                "notice_type": alert_info["notice_type"],
                "labels": alert_info["labels"],
                "annotations": alert_info["annotations"],
                "value": alert_info["value"],
                "maintain_begin": None,
                "maintain_end": None,
                "starts_at": datetime.now(),
                "ends_at": None,
                "comment": ""
            }
            record_info = NCN9eAlarmRecord.add(db, data)
            if not record_info:
                logger.error(f"n9e firing alarm record add to database failed")
                return False
            record_info = record_info.object_to_dict(db)
            record_info.pop("update_at")
            logger.info(f"n9e firing alarm record add to database success")
        else:
            # 存在告警状态的记录， 更新状态
            record_info["number"] += 1
            record_info.pop("update_at")
            result = NCN9eAlarmRecord.update(db, record_info)
            if not result:
                logger.error(f"n9e firing alarm record update to database failed")
                return False
            logger.info(f"n9e firing alarm record update to database success")
            
        return record_info



    def alert_to_database_resolved(self, db: Session, alert_info: dict):
        # 收到恢复信息
        record_info = NCN9eAlarmRecord.get_object_info_by_alert(db, alert_info, "firing")
        if record_info:
            # 告警恢复， 更新状态
            record_info["number"] += 1
            record_info["status"] = alert_info["status"]
            record_info["ends_at"] = datetime.now()
            record_info.pop("update_at")
            result = NCN9eAlarmRecord.update(db, record_info)
            if not result:
                logger.error(f"n9e resolved alarm record update to database failed")
                return False
            logger.info(f"n9e resolved alarm record update to database success")
        else:
            # 收到重复的恢复信息，
            record_info = NCN9eAlarmRecord.get_object_info_by_alert(db, alert_info, "resolved")
            if not record_info:
                logger.error(f"n9e repeated resolved alarm record not exist")
                return False
            
            # 重复更新状态,n9e有个订阅机制，但有多个订阅规则匹配上同一告警，则会同时发送多条订阅消息（告警和恢复），暂时用同一条记录来处理。
            record_info["number"] += 1
            record_info["status"] = alert_info["status"]
            record_info["ends_at"] = datetime.now()
            record_info.pop("update_at")
            result = NCN9eAlarmRecord.update(db, record_info)
            if not result:
                logger.error(f"n9e repeated resolved alarm record update to database failed")
                return False
            logger.info(f"n9e repeated resolved alarm record update to database success")
        
        return record_info


    def alert_to_database(self, db: Session, alert_info: dict):
        """
        告警信息写入数据库
        """
        
        if alert_info["status"] == "firing":
            record_info = self.alert_to_database_firing(db, alert_info)
            if not record_info:
                logger.error(f"firing: alert to database failed")
                return False
        else:
            record_info = self.alert_to_database_resolved(db, alert_info)
            if not record_info:
                logger.error(f"resolved: alert to database failed")
                return False
                
        return record_info
        


    def get_alert_info(self, db: Session, data: dict):
        """
        从原始的告警信息中，获取告警信息
        """

        alert_info = {
            "name": data["rule_name"],
            "cluster": "n9e",
            "classify": data["group_name"],
            "status": "resolved" if data["is_recovered"] else "firing",
            "target": data["target_ident"],
            "fingerprint": data["hash"],
            "notify_users": [],
            "notice_type": "alarm",
            "labels": data["tags"],
            "annotations": data["rule_note"],
            "value": data["trigger_value"],
            "department": data["group_name"].split("-")[0]
        }
        
        # 是否配置告警目标的标签
        for tag in data["tags"]:
            if "alarm_target=" in tag:
                alert_info["target"] = tag.split("=")[1]
        
        # 区分规则告警和订阅告警
        if data["callbacks"]:
            # 现在的n9e版本V6ga6,订阅规则必须配置回调地址才会发送订阅信息，所以判断：有回调地址的告警信息为订阅消息。
            alert_info["notice_type"] = "subscribe"

        # 获取告警级别
        for level in ["critical", "major", "warning", "info", "nothing"]:
            if level in data["notify_channels"]:
                alert_info["level"] = level
                break
        else:
            alert_info["level"] = "warning"
            
        # 获取通知人信息
        for notify_user in data["notify_users_obj"]:
            if notify_user["username"] in ["root"]:
                continue
            
            alert_info["notify_users"].append({
                "name": notify_user["username"],
                "telephone": notify_user["phone"],
                "mailbox": notify_user["email"],
                "comment": "n9e user"
            })
        

        logger.info(f"过滤后的告警信息 => {alert_info}")
        return alert_info



    def handle_alerts(self, db: Session, data: dict):
        try:
            logger.info(f"收到n9e告警信息=>{data}")

            # 从源数据获取告警信息
            alert_info = self.get_alert_info(db, data)

            # 更新数据库中的记录信息
            record_info = self.alert_to_database(db, alert_info)
            if not record_info:
                logger.error(f"n9e alert to database failed")
                return False
            
            # 发送告警信息
            self.alert_to_notice(db, alert_info, record_info)

            logger.info(f"处理完成:{alert_info}")

        except Exception as e:
            message = f"handle n9e alarm error"
            logger.exception(f"{message} => {e}")
            return False

        return True



    def post_n9e_alarm(self, db: Session, data: dict):
        try:
            result = self.handle_alerts(db, data)
            if not result:
                # 处理失败，发送信息到企业群
                notice_msg = f"{config.NOTICE_CENTER_N9E_NAME}: 处理n9e告警信息失败, 请及时查看"
                result = self.notice_tools.send_wechat_group(notice_msg)
                if not result:
                    logger.error(f"handle_alerts send to wechat_group failed")
                
                logger.error(notice_msg)
                return False
                
        except Exception as e:
            message = f"post n9e alarm error"
            logger.exception(f"{message} => {e}")
            return resp_500(message)
        
        logger.info("handle n9e alarm success")
        return True
        
            
    





def main():
    from common.database import SessionLocal
    db = SessionLocal()
    nc_n9e_alarm_handler = NCN9eAlarmHandler()
    data = {
	'id': 12,
	'cate': 'host',
	'cluster': '',
	'datasource_id': 0,
	'group_id': 2,
	'group_name': '阿里云测试环境',
	'hash': '236ae3eed11fde27936bc6639ba20ff7',
	'rule_id': 1,
	'rule_name': '测试环境主机失联告警',
	'rule_note': '业务组 itg 机器：itg-k8s-haproxy失联',
	'rule_prod': 'host',
	'rule_algo': '',
	'severity': 3,
	'prom_for_duration': 60,
	'prom_ql': '',
	'rule_config': {
		'queries': [{
			'key': 'group_ids',
			'op': '==',
			'values': [2]
		}],
		'triggers': [{
			'duration': 30,
			'severity': 3,
			'type': 'target_miss'
		}]
	},
	'prom_eval_interval': 30,
	'callbacks': [],
	'runbook_url': '',
	'notify_recovered': 1,
	'notify_channels': ['warning'],
	'notify_groups': ['2'],
	'notify_groups_obj': [{
		'id': 2,
		'name': '系统运维',
		'note': '',
		'create_at': 1686046701,
		'create_by': 'root',
		'update_at': 1686046717,
		'update_by': 'root'
	}],
	'target_ident': 'itg-k8s-haproxy',
	'target_note': '',
	'trigger_time': 1686126807,
	'trigger_value': '83',
	'tags': ['__name__=target_miss', 'busigroup=itg', 'ident=itg-k8s-haproxy', 'rulename=测试环境主机失联告警'],
	'annotations': {},
	'is_recovered': False,
	'notify_users_obj': [{
		'id': 1,
		'username': 'root',
		'nickname': '超管',
		'phone': '',
		'email': '',
		'portrait': '',
		'roles': ['Admin'],
		'contacts': {},
		'maintainer': 0,
		'create_at': 1686032756,
		'create_by': 'system',
		'update_at': 1686032756,
		'update_by': 'system',
		'admin': True
	}, {
		'id': 2,
		'username': 'boll.lai',
		'nickname': '黄明中',
		'phone': '18682003157',
		'email': 'boll.lai@welab-inc.com',
		'portrait': '',
		'roles': ['Admin'],
		'contacts': {},
		'maintainer': 0,
		'create_at': 1686046671,
		'create_by': 'root',
		'update_at': 1686046671,
		'update_by': 'root',
		'admin': True
	}],
	'last_eval_time': 1686127407,
	'last_sent_time': 1686126807,
	'notify_cur_number': 1,
	'first_trigger_time': 1686126807
}
    
    nc_n9e_alarm_handler.post_n9e_alarm(db, data)
    db.close()
    

if __name__ == "__main__":
    main()





